/*
 * Copyright 2008 Wilfred Springer
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.agilejava.javaone;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.sun.corba.se.spi.orbutil.threadpool.ThreadPool;
import com.sun.org.apache.xpath.internal.XPathAPI;

public class ScheduleBuilderSessionCatalog extends AbstractSessionCatalog
        implements SessionCatalog {

    private DateTimeFormatter format;

    private String username;

    private String password;

    public ScheduleBuilderSessionCatalog(String username, String password,
            int nrThreads) {
        super(nrThreads);
        DateTimeZone zone = DateTimeZone.forOffsetHours(-8);
        DateTimeZone.setDefault(zone);
        format = DateTimeFormat.forPattern("MM/dd/YYYY hh:mm aa");
        this.username = username;
        this.password = password;
    }

    public List<ScheduledSession> getSessions() throws DataAccessException {
        try {
            HttpClient client = new HttpClient();
            client.getParams().setCookiePolicy(
                    CookiePolicy.BROWSER_COMPATIBILITY);
            GetMethod get = new GetMethod(
                    "http://www.cplan.com/javaone2008/schedulebuilder");
            client.executeMethod(get);
            get.releaseConnection();
            get = new GetMethod("http://www28.cplan.com/sb191/login.jsp");
            client.executeMethod(get);
            get.releaseConnection();
            PostMethod post = new PostMethod(
                    "http://www28.cplan.com/sb191/srvcontroller");
            post.setParameter("action", "newLogin");
            post.setParameter("il", "191-1");
            post.setParameter("ic", "191");
            post.setParameter("ilg", "english");
            post.setParameter("username", username);
            post.setParameter("password", password);
            client.executeMethod(post);
            post.releaseConnection();
            post = new PostMethod("http://www28.cplan.com/sb191/srvcontroller");
            post.setParameter("action", "searchSessions");
            post.setParameter("isort", "%3E");
            post.setParameter("isort_type", "");
            post.setParameter("icriteria1", " ");
            post.setParameter("icriteria2", " ");
            post.setParameter("icriteria5", "");
            post.setParameter("icriteria3", " ");
            post.setParameter("icriteria7", "");
            post.setParameter("icriteria6", "");
            post.setParameter("icriteria4", " ");
            client.executeMethod(post);
            Node doc = loadDocument(post.getResponseBodyAsStream());
            post.releaseConnection();
            return loadSessions(doc);
        } catch (HttpException e) {
            throw new DataAccessException("Failed to perform HTTP request.", e);
        } catch (IOException e) {
            throw new DataAccessException("Communication failure.", e);
        } catch (SAXException e) {
            throw new DataAccessException("Failed to deal with SAX parser.", e);
        } catch (ParserConfigurationException e) {
            throw new DataAccessException(
                    "Failed to construct SAX parser correctly.", e);
        } catch (TransformerException e) {
            throw new DataAccessException("Failed to evaluate XPath.", e);
        }
    }

    private List<ScheduledSession> loadSessions(Node doc)
            throws TransformerException, ParserConfigurationException,
            IOException, SAXException {
        List<ScheduledSession> sessions = new LinkedList<ScheduledSession>();
        NodeList sessionNodes = XPathAPI
                .selectNodeList(
                        doc,
                        "//html:table[html:form[@action='srvcontroller']]/html:form/html:tr[position()>2]");
        int total = sessionNodes.getLength() - 1;
        logger.logSessionCount(total);
        for (int i = 0; i < total; i++) {
            Node sessionNode = sessionNodes.item(i);
            ScheduledSession session = new ScheduledSession();
            session.setId(XPathAPI.eval(sessionNode,
                    "html:td[position()=3]/text()").toString().trim());
            logger.logLoadingSession(session.getId(), i + 1, total);
            session.setTitle(XPathAPI.eval(sessionNode,
                    "html:td[position()=4]//html:a/text()").toString());
            String detailsHref = XPathAPI.eval(sessionNode,
                    "html:td[position()=4]//html:a/@href").toString();
            session.setDetailsURL(extractURL(detailsHref));
            NodeList schedule = XPathAPI.selectNodeList(sessionNode,
                    "html:td[position()=6]//text()");
            session.setRegistered(XPathAPI.eval(sessionNode,
                    "html:td[position()=2]/html:center/text()").toString()
                    .contains("Remove From Schedule"));
            DateTime start = format.parseDateTime(schedule.item(1)
                    .getTextContent().trim()
                    + " "
                    + schedule.item(2).getTextContent().split("-")[0].trim());
            DateTime end = format.parseDateTime(schedule.item(1)
                    .getTextContent().trim()
                    + " "
                    + schedule.item(2).getTextContent().split("-")[1].trim());
            session.setInterval(new Interval(start, end));
            addDetails(session);
            NodeList presenterNodes = XPathAPI.selectNodeList(sessionNode,
                    "html:td[position()=5]/html:a");
            for (int j = 0; j < presenterNodes.getLength(); j++) {
                Node presenterNode = presenterNodes.item(j);
                Presenter presenter = new Presenter();
                presenter.setName(XPathAPI.eval(presenterNode, "text()")
                        .toString());
                presenter.setDetailsURL(extractURL(XPathAPI.eval(presenterNode,
                        "@href").toString()));
                addDetails(presenter);
                session.getPresenters().add(presenter);
            }
            sessions.add(session);
        }
        return sessions;
    }

    protected void addDetails(final Session session)
            throws ParserConfigurationException, IOException, SAXException,
            TransformerException {
        getExecutorService().execute(new Runnable() {
            public void run() {
                try {
                    Node detailNode = loadDocument(new URL(session
                            .getDetailsURL()));
                    Node valueNode = XPathAPI.selectSingleNode(detailNode,
                            "//html:body/html:table[position()=2]");
                    session.setSummary(XPathAPI.eval(valueNode,
                            "html:tr[position()=4]/html:td[position()=2]")
                            .toString().trim());
                } catch (Exception e) {
                    e.printStackTrace(System.err);
                    // logger.logFailedDownloadingSessionDetails(session.getId());
                }
                // Let's prevent DoS
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        });
    }

    @Override
    public String getBaseURL() {
        return "http://www28.cplan.com/sb191/";
    }

}
